package info.magnolia.blossom.sample.cv.service.impl;

import info.magnolia.blossom.sample.cv.model.CVSkills;
import info.magnolia.blossom.sample.cv.model.CurriculumVitae;
import info.magnolia.blossom.sample.cv.model.Experience;
import info.magnolia.blossom.sample.cv.service.CVService;
import info.magnolia.blossom.sample.cv.service.OCMUtils;
import info.magnolia.jcr.util.NodeUtil;
import org.apache.commons.lang.StringUtils;
import org.apache.jackrabbit.ocm.manager.ObjectContentManager;
import org.apache.jackrabbit.ocm.query.Filter;
import org.apache.jackrabbit.ocm.query.Query;
import org.apache.jackrabbit.ocm.query.QueryManager;
import org.apache.log4j.Logger;
import org.joda.time.DateTime;
import org.joda.time.Months;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.query.QueryResult;
import javax.jcr.query.Row;
import javax.jcr.query.RowIterator;
import javax.servlet.http.HttpSession;
import java.util.*;

/**
 * Created with IntelliJ IDEA.
 * User: peterbalazs
 * Date: 2/18/13
 * Time: 6:03 PM
 * To change this template use File | Settings | File Templates.
 */
public class CVServiceImpl implements CVService {

    public static final String PARAM_SESSION_CV = "cvobject";

    /**
     * Cache.
     * Often used CVs don't need to be loaded from the JCR all the time.
     */
    private final Map<String, CurriculumVitae> cvCache = new HashMap<String, CurriculumVitae>();

    /**
     * Class logger.
     */
    private static final Logger logger = Logger.getLogger(CVServiceImpl.class);

    @Override
    public List<CurriculumVitae> getAllCVs() {
        return getAllCVs(null);
    }

    @Override
    public List<CurriculumVitae> getAllCVs(final String searchCriteria) {
        final List<CurriculumVitae> cvs = new ArrayList<CurriculumVitae>();
        final ObjectContentManager ocm = OCMUtils.getObjectContentManager();
        final QueryManager queryManager = ocm.getQueryManager();
        final Filter filter = queryManager.createFilter(CurriculumVitae.class);
        final Set<String> foundIds = new HashSet<String>();
        filter.setScope("/");
        if (StringUtils.isNotBlank(searchCriteria)) {
            String query = "SELECT parent.* FROM [nt:base] AS parent LEFT OUTER JOIN [nt:base] AS child1 ON ISCHILDNODE(child1,parent)"
                + " LEFT OUTER JOIN [nt:base] as child2 on ischildnode(child2, child1)"
                + " WHERE parent.ocm_classname = 'info.magnolia.blossom.sample.cv.model.CurriculumVitae'"
                + " and (contains(parent.*, 'ssqq') or contains(child2.*, 'ssqq'))";
            query = query.replaceAll("ssqq", searchCriteria.trim());
            try {
                final QueryResult queryResult = ocm.getSession().getWorkspace().getQueryManager().createQuery(query.toString(), javax.jcr.query.Query.JCR_SQL2).execute();
                final RowIterator rows = queryResult.getRows();
                while (rows.hasNext()) {
                    final Row row = rows.nextRow();
                    final Node node = row.getNode("parent");
                    final CurriculumVitae cv = (CurriculumVitae) ocm.getObject(CurriculumVitae.class, node.getPath());
                    if (foundIds.contains(cv.getId())) {
                        continue;
                    }
                    foundIds.add(cv.getId());
                    cvs.add(cv);
                }
                return cvs;
            } catch (Exception e) {
                logger.error("Error searching for nodes with criteria " + searchCriteria, e);
                return cvs;
            }
        }
        final Query query = queryManager.createQuery(filter);
        final Collection collection =  ocm.getObjects(query);
        if (collection != null) {
            for (Object o : collection) {
                cvs.add((CurriculumVitae) o);
            }
        }
        return cvs;
    }

    @Override
    public CurriculumVitae getCV(final String id){

        logger.debug("START getCV");
        logger.debug("ID = " + id);

        final CurriculumVitae cacheCV = cvCache.get(id);
        if (cacheCV != null) {
            return cacheCV;
        }
        final Object jcrObject = OCMUtils.getObjectContentManager().getObject(buildCVPath(id));
        if (jcrObject == null) {
            return null;
        }
        if (jcrObject instanceof CurriculumVitae) {
            return (CurriculumVitae) jcrObject;
        } else {
            throw new IllegalStateException("For ID " + id + " excepted object of type " + CurriculumVitae.class.getCanonicalName()
                    + " but found " + jcrObject.getClass().getCanonicalName());
        }
    }

    @Override
    public void addCV(final CurriculumVitae cv) {

        logger.debug("START addCV");
        logger.debug("CV: " + cv);

        final String id = generateId();
        cv.setId(id);
        cv.setPath(buildCVPath(id));
        removeUnusedListElements(cv);
        final ObjectContentManager ocm = OCMUtils.getObjectContentManager();
        ocm.insert(cv);
        ocm.save();
        cvCache.put(id, cv);
    }

    @Override
    public void udpateCV(CurriculumVitae cv) {

        logger.debug("START updateCV()");
        logger.debug("CV: " + cv);

        if (cv == null || StringUtils.isBlank(cv.getPath()) || StringUtils.isBlank(cv.getId())) {
            throw new NullPointerException("CV is null or identifier(s) not set");
        }
        removeUnusedListElements(cv);
        final ObjectContentManager ocm = OCMUtils.getObjectContentManager();
        ocm.update(cv);
        ocm.save();
        cvCache.put(cv.getId(), cv);
    }

    private void removeUnusedListElements(final CurriculumVitae cv) {
        if (cv == null) {
            return;
        }
        if (cv.getLanguages() != null) {
            final Iterator<CVSkills> it = cv.getLanguages().iterator();
            while (it.hasNext()) {
                final CVSkills lang = it.next();
                if (StringUtils.isBlank(lang.getName())) {
                    it.remove();
                }
            }
            for (CVSkills l : cv.getLanguages()) {
                l.setParentId(cv.getId());
            }
        }
        if (cv.getSkills() != null) {
            final Iterator<CVSkills> it = cv.getSkills().iterator();
            while (it.hasNext()) {
                final CVSkills skill = it.next();
                if (StringUtils.isBlank(skill.getName())) {
                    it.remove();
                }
            }
            for (CVSkills s : cv.getSkills()) {
                s.setParentId(cv.getId());
            }
        }
        if (cv.getExperiences() != null) {
            final Iterator<Experience> it = cv.getExperiences().iterator();
            while (it.hasNext()) {
                final Experience exp = it.next();
                if (StringUtils.isBlank(exp.getEmployer())) {
                    it.remove();
                }
            }
        }
    }

    @Override
    public void removeCV(CurriculumVitae cv) {

        logger.debug("START removeCV()");
        logger.debug("CV: " + cv);

        if (cv == null || StringUtils.isBlank(cv.getPath()) || StringUtils.isBlank(cv.getId())) {
            throw new NullPointerException("CV is null or identifier(s) not set");
        }
        final ObjectContentManager ocm = OCMUtils.getObjectContentManager();
        final String id = cv.getId();
        ocm.remove(cv);
        ocm.save();
        cvCache.remove(id);
    }

    @Override
    public void updateTimeSpentAtEmployer(final CurriculumVitae cv) {
        if (cv == null) {
            return;
        }
        if (cv.getExperiences() == null) {
            return;
        }
        final Date now = new Date();
        for (Experience exp : cv.getExperiences()) {
            if (exp.getFromDate() == null || exp.getFromDate().after(now)) {
                exp.setNumberOfMonths("-");
                continue;
            }
            int months = Months.monthsBetween(new DateTime(exp.getFromDate()).withDayOfMonth(1), new DateTime(exp.getToDate() == null ? now : exp.getToDate()).withDayOfMonth(1)).getMonths();
            final int years = months / 12;
            months = months % 12;
            final StringBuilder sb = new StringBuilder();
            if (years > 0) {
                sb.append(years).append(" year");
                if (years > 1) {
                    sb.append('s');
                }
                if (months > 0) {
                    sb.append(", ");
                }
            }
            if (months > 0) {
                sb.append(months).append(" month");
                if (months > 1) {
                    sb.append('s');
                }
            }
            exp.setNumberOfMonths(sb.toString());
        }
    }

    private String buildCVPath(final String id) {
        return "/cv" + id;
    }

    private String generateId() {
        // TODO implement a
        return System.currentTimeMillis() + "";
    }
}
